use std::fs::File;
use std::io::{Read, Seek, SeekFrom};

#[derive(Debug, PartialEq)]
pub enum Token {
    Name(String),
    String(String),
    Eos,
}

#[derive(Debug)]
pub struct Lex {
    input: File,
}

impl Lex {
    pub fn new(input: File) -> Self {
        Self { input }
    }

    pub fn next(&mut self) -> Token {
        // 1. 读取 字符; '\0' 表示结束
        let ch = self.read_char();
        match ch {
            // 2 遇到 空格 回车 换行 制表符 继续下一个
            ' ' | '\r' | '\n' | '\t' => self.next(),
            // 3. NUL 结束
            '\0' => Token::Eos,
            // 4. 双引号
            '"' => {
                // literal String
                // 4-1.生成一个空String类型
                let mut s = String::new();

                // 4-2. 循环执行read_char获取下一个字符，直到读取到结束 获取下一个双引号
                loop {
                    match self.read_char() {
                        '\0' => panic!("unfinished literal string"),
                        '"' => break,
                        ch => s.push(ch),
                    }
                }
                Token::String(s)
            }
            // 5. 处理字母和_
            'A'..='Z' | 'a'..='z' | '_' => {
                // Name
                let mut name = String::new();
                name.push(ch);
                loop {
                    match self.read_char() {
                        '\0' => break,
                        '_' => name.push('_'),
                        ch if ch.is_alphanumeric() => name.push(ch),
                        _ => {
                            self.input.seek(SeekFrom::Current(-1)).unwrap();
                            break;
                        }
                    }
                }
                Token::Name(name)
            }

            _ => panic!("unexpected char: {ch}"),
        }
    }

    fn read_char(&mut self) -> char {
        // 1. 创建缓冲区
        let mut buf: [u8; 1] = [0];
        // 2. 使用File.read 读取内容到缓冲区 一个字节，读取完成返回缓冲区长度
        //  如果为0 ，表示读取完成 ，则返回 '\0' (char类型 ASCII null)
        if self.input.read(&mut buf).unwrap() == 1 {
            buf[0] as char
        } else {
            '\0'
        }
    }
}

#[cfg(test)]

mod tests {
    use super::*;

    #[test]
    fn test_parse_file() {
        let f = File::open("../print.lua").unwrap();
        let mut lex_1 = Lex::new(f);
        // assert_eq!(lex_1.read_char(), 'p');
        let f0 = File::open("../0_print.lua").unwrap();
        let mut lex_0 = Lex::new(f0);
        assert_eq!(lex_0.read_char(), '\0');

        assert_eq!(lex_1.next(), Token::Name(String::from("print")));
        assert_eq!(lex_1.next(), Token::String("HELLO,WORLD!".to_string()));
        assert_eq!(lex_1.next(), Token::Eos);
    }
}
